/*
 * Copyright (c) 2008-2009 Ravenhearte Design
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Ravenhearte Design' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ravenhearte.hardboild.gamestates;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import org.ravenhearte.hardboild.gamelogic.*;
import org.ravenhearte.hardboild.gamelogic.Skill.OnSelect;
import org.ravenhearte.hardboild.gamelogic.loadouts.Loadouts;
import org.ravenhearte.hardboild.gamelogic.maps.TutorialMap;
import org.ravenhearte.hardboild.gui.*;

import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Spatial.CullHint;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueueManager;
import com.jmex.game.state.GameState;

public class Tutorial extends GameState implements InputState {
	private Camera cam;
	private Vector3f cameraAnchor, cameraFocus, sphericalCC;	// Camera anchor
												// and spherical coordinates
	private Node guiNode;
	private GameMap tutorialMap;
	private HashMap<String, Button> buttons;
	private LinkedList<Container> scrollables;
	private LinkedList<String> enemyNames;
	private boolean scrollLeft, scrollRight, scrollUp, scrollDown,
		rightClick, clearTime;
	private enum Mode { PLACEMENT, SELECT, TURN, CONTINUETURN };
	private Mode mode;
	
	public Tutorial() {
		name = "Tutorial";
		
		guiNode = new Node("Tutorial Gui Node");
		buttons = new HashMap<String, Button>();
		scrollables = new LinkedList<Container>();
		enemyNames = new LinkedList<String>();

		enemyNames.add("Julien");
		enemyNames.add("Brutus");
		enemyNames.add("Kesar");
		enemyNames.add("Franco");
		enemyNames.add("Adolfo");
		enemyNames.add("Frederick");
		enemyNames.add("Nicodemus");
		enemyNames.add("Fitzhenry");
		enemyNames.add("Harold");
		enemyNames.add("Antoine");
        
		tutorialMap = TutorialMap.getMap();
		
		placementStage();
		
        guiNode.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        guiNode.setLightCombineMode(Spatial.LightCombineMode.Off);
		guiNode.updateRenderState();
		
		cam = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
		
		cameraAnchor = new Vector3f(0, 0, 0);
		cameraFocus = cameraAnchor.clone();
		sphericalCC = new Vector3f(15, FastMath.PI / 4, 3 * FastMath.PI / 4);
		
		Vector3f cameraLoc = new Vector3f(0, 0, 0);

		cameraLoc.x = sphericalCC.x * FastMath.sin(sphericalCC.y)
			* FastMath.cos(sphericalCC.z) + cameraAnchor.x;
		cameraLoc.y = sphericalCC.x * FastMath.sin(sphericalCC.y)
			* FastMath.sin(sphericalCC.z) + cameraAnchor.y;
		cameraLoc.z = sphericalCC.x * FastMath.cos(sphericalCC.y)
			+ cameraAnchor.z;
		
		
		cam.setLocation(cameraLoc);
		cam.lookAt(cameraAnchor, new Vector3f(0, 0, 1));
		
		cam.setLeft(cam.getLeft().mult(-1));
	}
	
	private void placementStage() {
		tutorialMap.placementStage();
		mode = Mode.PLACEMENT;
		
		Container partyMenu = new Container("Party Menu", 30, 30, 300, 708);
		partyMenu.toggleScrollable();
		scrollables.add(partyMenu);
		
		partyMenu.setBorderedBackground("titlescreen.png", 0, 768);
		
		Button[] scrollButtons = partyMenu.getButtons();
		
		LinkedList<GameCharacter> characters = Manager.theParty.getCharacters();
		ListIterator<GameCharacter> it = characters.listIterator();
		
		while(it.hasNext()) {
			GameCharacter character = it.next();
			
			Container characterContainer = new Container(character.getName()+
					"'s Container", 0, 0, 300, 680);
			characterContainer.toggleManagedLayout();

			TextElement temp = new TextElement(character.getName()+
					"'s Label", ColorRGBA.black, 45);
			temp.setLocation(130, 0);
			temp.setAlignment(Element.Alignment.CENTERED);
			temp.print(character.getName());
			characterContainer.addElement(temp);
			
			character.getPortrait().setLocation(0, 45);
			characterContainer.addElement(character.getPortrait());
			
			partyMenu.addElement(characterContainer);
		}

		buttons.put(scrollButtons[0].getName(), scrollButtons[0]);
		buttons.put(scrollButtons[1].getName(), scrollButtons[1]);

		TextElement temp2 = new TextElement("Fight!", ColorRGBA.black, 45);
		temp2.setLocation(923, 682);
		temp2.setAlignment(Element.Alignment.CENTERED);
		temp2.print("Fight!");
		guiNode.attachChild(temp2.getSpatial());
		
		TextButton temp = new TextButton("Start Fight", ColorRGBA.white, 45);
		temp.setLocation(920, 680);
		temp.setAlignment(Element.Alignment.CENTERED);
		temp.print("Fight!");
		temp.setState("MouseOut", ColorRGBA.white);
		temp.setState("MouseOver", ColorRGBA.gray);
		temp.setState("MouseClick", ColorRGBA.darkGray);
		buttons.put(temp.getName(), temp);
		guiNode.attachChild(temp.getSpatial());
		
		guiNode.attachChild(partyMenu.getSpatial());
	}
	
	private void fightStage() {
		Random rand = new Random();
		//Create and place enemies
		int numberOfEnemies = rand.nextInt(4) + 1;
		
		for(int i = 0; i < numberOfEnemies; i++) {
			int whichName = rand.nextInt(enemyNames.size());
			String enemyName = enemyNames.get(whichName);
			enemyNames.remove(whichName);
			
			GameCharacter enemy = new GameCharacter(enemyName, ColorRGBA.red);
			Loadouts.Standard(enemy);
			
			tutorialMap.placeCharacter(tutorialMap.randomTile(), enemy);
		}
		
		tutorialMap.fightStage();		
		tutorialMap.generateInitiative();

		mode = Mode.TURN;
		clearTime = true;
	}
	
	private void turn() {
		GameCharacter current = tutorialMap.getCurrentCharacter();
		
		cameraFocus = current.getSpatial().getLocalTranslation();

		LinkedList<String> charLocation = new LinkedList<String>();
		charLocation.add(GameMap.intArrayToString(current.getLocation()));
		tutorialMap.setTileGeometry(charLocation, ColorRGBA.green);
		
		current.increaseWill(10);
		
		final Container turnMenu = current.getTurnMenu(true);
		
		scrollables.addAll(current.getScrollables());
		buttons.putAll(current.getButtons());

		GameTaskQueueManager.getManager().render(new Callable<Object>() {
		    public Object call() throws Exception {
				guiNode.attachChild(turnMenu.getSpatial());
				guiNode.updateRenderState();
		      return null;
		    }
		  });
		mode = Mode.CONTINUETURN;
	}
	
	private void continueTurn() {
		GameCharacter current = tutorialMap.getCurrentCharacter();
		if(mode == Mode.CONTINUETURN) {
			LinkedList<String> charLocation = new LinkedList<String>();
			charLocation.add(GameMap.intArrayToString(current.getLocation()));
			tutorialMap.setTileGeometry(charLocation, ColorRGBA.green);
		}
		final Container turnMenu = current.getTurnMenu(false);
		
		scrollables.addAll(current.getScrollables());
		buttons.putAll(current.getButtons());


		GameTaskQueueManager.getManager().render(new Callable<Object>() {
		    public Object call() throws Exception {
				guiNode.attachChild(turnMenu.getSpatial());
				guiNode.updateRenderState();
		      return null;
		    }
		  });
	}
	
	private void placeCharacterAt(GameTile tile) {
		Container partyMenu = new Container("temp", 0, 0, 0, 0);
		Iterator<Container> it = scrollables.listIterator();
		while(it.hasNext()) {
			Container scrollable = it.next();
			if(scrollable.getName().compareTo("Party Menu") == 0) {
				partyMenu = scrollable;
				break;
			}
		}
		Container characterContainer = (Container)partyMenu.getCurrentCell();
		String characterName = characterContainer.getName().replace("'s Container", "");
				
		LinkedList<GameCharacter> characters = Manager.theParty.getCharacters();
		ListIterator<GameCharacter> lit = characters.listIterator();
		while(lit.hasNext()) {
			GameCharacter character = lit.next();
			if (character.getName().compareTo(characterName) == 0) {
				tutorialMap.placeCharacter(tile, character);
			}
		}
	}
	
	private void executeSkillAt(GameTile tile) {
		GameCharacter current = tutorialMap.getCurrentCharacter();
		
		Skill skill = current.getSkill(current.getCurrSkillSet(), 
				current.getCurrSkill());
		
		skill.performSkill(tile);
		current.getTurnMenu(true);
		mode = Mode.CONTINUETURN;
		clearTime = true;
	}

	@Override
	public void cleanup() {
	}

	@Override
	public void render(float tpf) {
		DisplaySystem.getDisplaySystem().getRenderer().
			draw(tutorialMap.getGeometry());
		DisplaySystem.getDisplaySystem().getRenderer().
			draw(tutorialMap.getTileGeometry());
		DisplaySystem.getDisplaySystem().getRenderer().
			draw(guiNode);
	}

	@Override
	public void update(float tpf) {
		Camera cam = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
		Vector3f direction = cam.getDirection();
		Vector3f left = cam.getLeft();
		direction.z = 0;
		left.z = 0;
		
        if (cameraAnchor.distance(cameraFocus) < .1) {
        	cam.setLocation(cameraFocus);
        }
        else {
            Vector3f moveDirection = cameraFocus.subtract(cameraAnchor);
            moveDirection.normalizeLocal();
        	cameraAnchor.addLocal(moveDirection.mult(tpf * 5));
        }
		
		if (scrollLeft) {
			cameraAnchor = cameraAnchor.add(left.normalize().mult(tpf * 5f));
			cameraFocus = cameraFocus.add(left.normalize().mult(tpf * 5f));
		}
		if (scrollRight) {
			cameraAnchor = cameraAnchor.subtract(left.normalize().mult(tpf * 5f));
			cameraFocus = cameraFocus.subtract(left.normalize().mult(tpf * 5f));
		}
		if (scrollUp) {
			cameraAnchor = cameraAnchor.add(direction.normalize().mult(tpf * 5f));
			cameraFocus = cameraFocus.add(direction.normalize().mult(tpf * 5f));
		}
		if (scrollDown) {
			cameraAnchor = cameraAnchor.subtract(direction.normalize().mult(tpf * 5f));
			cameraFocus = cameraFocus.subtract(direction.normalize().mult(tpf * 5f));
		}
		
		Vector3f cameraLoc = new Vector3f(0, 0, 0);

		cameraLoc.x = sphericalCC.x * FastMath.sin(sphericalCC.y)
			* FastMath.cos(sphericalCC.z) + cameraAnchor.x;
		cameraLoc.y = sphericalCC.x * FastMath.sin(sphericalCC.y)
			* FastMath.sin(sphericalCC.z) + cameraAnchor.y;
		cameraLoc.z = sphericalCC.x * FastMath.cos(sphericalCC.y)
			+ cameraAnchor.z;
		
		
		cam.setLocation(cameraLoc);
		cam.lookAt(cameraAnchor, new Vector3f(0, 0, 1));
		
		cam.setLeft(cam.getLeft().mult(-1));

		tutorialMap.getGeometry().updateGeometricState(tpf, true);
		tutorialMap.getTileGeometry().updateGeometricState(tpf, true);
		guiNode.updateGeometricState(tpf, true);
		
		if(clearTime) {
			if(!buttons.isEmpty()) {
				buttons.clear();
				scrollables.clear();
				guiNode.detachAllChildren();
			}
			if(!tutorialMap.getCurrentCharacter().isAnimating()) {
				clearTime = false;
				switch(mode) {
				case TURN:
					turn();
					break;
				case CONTINUETURN:
				case SELECT:
					continueTurn();
					break;
				}
			}
			else {
				Vector3f follow = tutorialMap.getCurrentCharacter().getSpatial()
				.getLocalTranslation();
				float lead = cameraFocus.distance(follow);
				
				if(lead > 3) {
					cameraFocus = follow;
				}
			}
		}
	}
	
	@Override
	public boolean onKey(int keyCode, boolean pressed) {
		if (pressed) {
			switch(keyCode) {
			case KeyInput.KEY_LEFT:
				scrollLeft = true;
				break;
			case KeyInput.KEY_RIGHT:
				scrollRight = true;
				break;
			case KeyInput.KEY_UP:
				scrollUp = true;
				break;
			case KeyInput.KEY_DOWN:
				scrollDown = true;
				break;
			}
		}
		else {
			switch(keyCode) {
			case KeyInput.KEY_LEFT:
				scrollLeft = false;
				break;
			case KeyInput.KEY_RIGHT:
				scrollRight = false;
				break;
			case KeyInput.KEY_UP:
				scrollUp = false;
				break;
			case KeyInput.KEY_DOWN:
				scrollDown = false;
				break;
			}
		}
		return false;
	}

	@Override
	public boolean onMouseMove(int delta, int delta2, int x, int y) {
		if(rightClick) {
			float phi = sphericalCC.y + ((float)delta2 / 20f);
			float theta = sphericalCC.z + ((float)delta / 20f);
			
			if (phi < .1f) {
				phi = .1f;
			}
			else if (phi > (FastMath.PI / 2f - .1f)) {
				phi = (FastMath.PI / 2f - .1f);
			}
			sphericalCC.y = phi;
			sphericalCC.z = theta;
			return true;
		}
		Iterator<Button> it = buttons.values().iterator();
		boolean processed = false;
		
		while(it.hasNext() && !clearTime) {
			Button temp = it.next();
			if(temp.contains(x, y)) {
				if(!temp.isPressed()) {
					temp.onMouseOver();
				}
				processed = true;
			}
			else {
				if(!temp.isPressed()) {
					temp.onMouseOut();
				}
			}
		}
		
		if(!processed && (mode == Mode.PLACEMENT || mode == Mode.SELECT)) {
			GameTile tile = tutorialMap.findMousePick(x, y);
			if(tile != null) {
				tutorialMap.highlightTile(tile);
				processed = true;
			}
			else {
				tutorialMap.clearHighlight();
			}
		}
		else {
			tutorialMap.clearHighlight();
		}
		
		return processed;
	}

	@Override
	public boolean onMouseClick(int button, int x, int y) {
		if(button == 1) {
			rightClick = true;
			return true;
		}
		Iterator<Button> it = buttons.values().iterator();
		boolean processed = false;
		
		while(it.hasNext() && !clearTime) {
			Button temp = it.next();
			if(temp.contains(x, y)) {
				temp.onMouseClick();
				processed = true;
			}
			else {
				temp.onMouseOut();
			}
		}
		
		if(!processed) {
			GameTile tile = tutorialMap.findMousePick(x, y);
			if(tile != null) {
				switch(mode) {
				case PLACEMENT:
					placeCharacterAt(tile);
					break;
				case SELECT:
					executeSkillAt(tile);
					break;
				}
				processed = true;
			}
			else {
				tutorialMap.clearHighlight();
			}
		}
		
		return processed;
	}

	@Override
	public boolean onMouseRelease(int button, int x, int y) {
		if(button == 1) {
			rightClick = false;
			return true;
		}
		Iterator<Button> it = buttons.values().iterator();
		boolean processed = false;
		
		while(it.hasNext() && !clearTime) {
			Button temp = it.next();
			if(temp.contains(x, y)) {
				if(temp.isPressed() && temp.getSpatial().getCullHint() != CullHint.Always) {
					temp.onMouseRelease();
					doAction(temp.getName());
					processed = true;
				}
			}
			else {
				if(temp.isPressed()) {
					temp.onMouseRelease();
				}
				temp.onMouseOut();
			}
		}
		
		return processed;
	}
	
	@Override
	public boolean onWheel(int wheelDelta, int x, int y) {
		boolean processed = true;
		
		float rho = sphericalCC.x - ((float)wheelDelta / 100f);
		
		if (rho < 10) {
			rho = 10;
		}
		else if (rho > 100) {
			rho = 100;
		}
		
		sphericalCC.x = rho;

		return processed;
	}
	
	public void doAction(String action) {
		if(action.contains("Scroll")) {
			Iterator<Container> it = scrollables.listIterator();
			while(it.hasNext()) {
				Container scrollable = it.next();
				if(action.contains(scrollable.getName())) {
					if(action.contains("Scroll Up")) {
						scrollable.scrollUp();
					}
					else if(action.contains("Scroll Down")) {
						scrollable.scrollDown();
					}
				}
			}
		}
		else if(action.contains("SKILLSET_")) {
			GameCharacter current = tutorialMap.getCurrentCharacter();
			String skillSetName = action.replace("SKILLSET_", "");
			current.selectSkillSet(skillSetName);
			clearTime = true;
		}
		else if(action.contains("SKILL_")) {
			GameCharacter current = tutorialMap.getCurrentCharacter();
			String skillName = action.replace("SKILL_", "");
			
			boolean selected = current.selectSkill(skillName);
			
			if(selected) {
				Skill skill = current.getSkill(current.getCurrSkillSet(), skillName);
			
				if(skill.onSelect() == OnSelect.TARGET) {
					tutorialMap.setTileGeometry(skill.getTargetRange(),
							skill.getColor());
					mode = Mode.SELECT;
				}
				clearTime = true;
			}
		}
		else if(action.contains("BACK_")) {
			GameCharacter current = tutorialMap.getCurrentCharacter();
			String where = action.replace("BACK_", "");
			if(where.compareTo("SKILLSET") == 0) {
				current.getTurnMenu(true);
			}
			else if(where.compareTo("SKILL") == 0) {
				current.selectSkillSet(current.getCurrSkillSet());
				mode = Mode.CONTINUETURN;
			}
			clearTime = true;
		}
		else if(action.compareTo("Start Fight") == 0) {
			fightStage();
		}
	}
}
